package com.idanchuang.component.consumer;

import com.alibaba.fastjson.JSON;
import com.idanchuang.component.base.exception.core.ExFactory;
import com.idanchuang.component.base.exception.exception.AbstractException;
import com.idanchuang.component.consumer.config.RpcConfig;
import com.idanchuang.component.consumer.servlet.ConsumerServletRequestContext;
import com.idanchuang.component.core.Dc;
import com.idanchuang.component.core.ServiceInfo;
import com.idanchuang.component.core.helper.EnvContext;
import com.idanchuang.component.logback.helper.LogContext;
import com.idanchuang.component.provider.api.util.RpcThreadLocalUtil;
import com.idanchuang.component.provider.api.util.RpcUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 *
 * feign请求拦截器
 * 所有用feign发出的请求的拦截器，注意是feign作为客户端发出请求的，而不是服务端。
 * 这个在在项目代码中的任何位置都可以，需要在每个服务端验证这个全局参数。
 * @author yjy
 */
public class FeignRequestInterceptor implements RequestInterceptor {

    private static final Logger log = LoggerFactory.getLogger(FeignRequestInterceptor.class);

    private final RpcConfig rpcConfig;
    @Autowired(required = false)
    private ConsumerServletRequestContext consumerServletRequestContext;

    public FeignRequestInterceptor(RpcConfig rpcConfig) {
        this.rpcConfig = rpcConfig;
    }

    @Override
    public void apply(RequestTemplate requestTemplate) {
        try {
            String path = requestTemplate.path();
            if ("/".equals(path)) {
                path = "";
            }
            //这里可以添加feign请求的全局参数
            String secretKey = rpcConfig.getSecretKey();
            List<String> extraSecretKeys = rpcConfig.getExtraSecretKeys();
            String[] signs = new String[1 + extraSecretKeys.size()];
            signs[0] = RpcUtil.sign(path, secretKey);
            for (int i = 0; i < extraSecretKeys.size(); i++) {
                signs[i + 1] = RpcUtil.sign(path, extraSecretKeys.get(i));
            }
            addHeader(requestTemplate, rpcConfig.getSignKey(), signs);
            // 扩展请求头
            if (!rpcConfig.getHeaders().isEmpty()) {
                for (Map.Entry<String, String> kv : rpcConfig.getHeaders().entrySet()) {
                    addHeader(requestTemplate, kv.getKey(), encodeUrl(kv.getValue()));
                }
            }
            // 将本地线程变量表存入header传递
            Map<String, String> map = RpcThreadLocalUtil.getAllFromThreadLocal();
            if (!map.isEmpty()) {
                String mapString = JSON.toJSONString(map);
                addHeader(requestTemplate, rpcConfig.getThreadLocalHeaderName(), encodeUrl(mapString));
            }
            Map<String, Collection<String>> queries = requestTemplate.queries();
            if (!queries.containsKey(Dc.RPC_FROM_NAME)) {
                // 添加来源参数 (添加在query中是为了记录访问日志时能从url中可读, 后期自己打印访问日志后可能被弃用)
                requestTemplate.query(Dc.RPC_FROM_NAME, Collections.singleton(ServiceInfo.getAppId()));
                // 在header中也放一份
                addHeader(requestTemplate, Dc.RPC_FROM_NAME, ServiceInfo.getAppId());
                String currentPath = EnvContext.getCurrentPath();
                if (currentPath != null) {
                    addHeader(requestTemplate, Dc.RPC_FROM_API_NAME, currentPath);
                }
                addHeader(requestTemplate, Dc.RPC_FROM_GROUP_NAME, ServiceInfo.getGroup());
                addHeader(requestTemplate, Dc.RPC_FROM_VERSION_NAME, ServiceInfo.getVersion());
            }
            // 扩展请求参数
            if (!rpcConfig.getQueries().isEmpty()) {
                for (Map.Entry<String, Collection<String>> kv : rpcConfig.getQueries().entrySet()) {
                    if (!queries.containsKey(kv.getKey())) {
                        requestTemplate.query(kv.getKey(), kv.getValue());
                    }
                }
            }
            // 传递自定义链路id
            String dcTraceIdHeaderName = EnvContext.getDcTraceIdHeaderName();
            if (!requestTemplate.headers().containsKey(dcTraceIdHeaderName)) {
                String dcTraceId = EnvContext.getDcTraceId();
                if (dcTraceId != null) {
                    addHeader(requestTemplate, dcTraceIdHeaderName, dcTraceId);
                }
            }
            // 传递环境变量
            if (EnvContext.getAttributeCount() > 0) {
                String attributes = JSON.toJSONString(EnvContext.getAttributes());
                attributes = URLEncoder.encode(attributes, "UTF-8");
                addHeader(requestTemplate, EnvContext.TRANSMIT_ATTRIBUTE_HEADER_NAME, attributes);
            }
            // 如果存在request上下游
            if (consumerServletRequestContext != null) {
                Set<String> ths = rpcConfig.getAllTransmitHeaders();
                if (!ths.isEmpty()) {
                    // 从当前请求上下文中获取header传递至下游
                    Map<String, List<String>> originHeaders = consumerServletRequestContext.getMultiHeaders(ths);
                    Set<String> currentHeaders = requestTemplate.headers().keySet();
                    originHeaders.forEach((k, v) -> {
                        if (v != null && !v.isEmpty()) {
                            if (!currentHeaders.contains(k)) {
                                addHeader(requestTemplate, k, v.toArray(new String[0]));
                            } else {
                                log.debug("transmit header canceled, cause currentHeaders contains key:{}", k);
                            }
                        }
                    });
                }
                Set<String> tqs = rpcConfig.getAllTransmitQueries();
                if (!tqs.isEmpty()) {
                    // 从当前请求上下文中获取queries传递至下游
                    Map<String, String[]> originQueries = consumerServletRequestContext.getMultiQueries(tqs);
                    Set<String> currentQueries = requestTemplate.queries().keySet();
                    originQueries.forEach((k, v) -> {
                        if (v != null && v.length > 0) {
                            if (!currentQueries.contains(k)) {
                                for (int i = 0; i < v.length; i++) {
                                    v[i] = encodeUrl(v[i]);
                                }
                                requestTemplate.query(k, v);
                            } else {
                                log.debug("transmit query canceled, cause currentQueries contains key:{}", k);
                            }
                        }
                    });
                }
            }
            log.debug("Rpc RequestHeaders > {}, Queries > {}", requestTemplate.headers(), requestTemplate.queries());
        } catch (Exception e) {
            LogContext.setKeyword("feign-interceptor-error");
            log.error("FeignRequestInterceptor failed", e);
            if (e instanceof AbstractException) {
                throw (AbstractException)e;
            }
        }
    }

    private void addHeader(RequestTemplate template, String key, String... values) {
        if (values == null || values.length == 0) {
            return;
        }
        int limit = rpcConfig.getHeaderLimit();
        if (limit > 0) {
            for (String value : values) {
                if (value == null) {
                    continue;
                }
                int length = value.getBytes(StandardCharsets.UTF_8).length;
                if (length > limit) {
                    throw ExFactory.throwSystem("Rpc request header too large, limit: {}, actual: {}",
                            limit, length);
                }
            }
        }
        template.header(key, values);
    }

    private static String encodeUrl(String origin) {
        try {
            return URLEncoder.encode(origin, "UTF-8");
        } catch (Exception e) {
            log.error("encodeUrl failed, origin: {}", origin, e);
        }
        return origin;
    }

}